Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use socket.sendall to send packets #984

Merged
merged 1 commit into from
Jan 22, 2022
Merged

Use socket.sendall to send packets #984

merged 1 commit into from
Jan 22, 2022

Conversation

hieplpvip
Copy link
Contributor

Currently, _send_packet doesn't check if all data has been sent. This is problematic when the number of problems is large, so the packet can't be sent in one pass. This PR fixes that.

@dmoj-build
Copy link
Collaborator

Can one of the admins verify this patch?

@@ -238,7 +238,13 @@ def _send_packet(self, packet: dict):
raw = zlib.compress(utf8bytes(json.dumps(packet)))
with self._lock:
try:
self.output.writelines((PacketManager.SIZE_PACK.pack(len(raw)), raw))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python is supposed to send everything with this call. What exactly is the problem you are seeing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We host a very large number of problems, so the handshake packet is quite large (over 100KBs). Everything is fine if both the judge and bridged are run on the same server, but when the judge is moved to a separate server, it fails to handshake.

From what I understand after reading CPython source code, writelines just calls write for every line, which in turn calls socket.send. The document for socket.send clearly notes that "Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data." (ref). In our case, raw is so large that socket.send(raw) could not send everything in one pass.

P/s: Before coming up with this fix, we have to monkey patch like this:

self.output.writelines((PacketManager.SIZE_PACK.pack(len(raw)), raw[:100000], raw[100000:]))

which handshakes successfully, so the problem seems to be indeed due to socket.send unable to send everything at once.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a Python bug with the makefile interface to sockets because it's supposed to make sockets look like normal streams and handle incomplete transmission. Out of curiosity, what version are you using and how many problems do you have?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The judge uses Python 3.9.7. Currently, we have around 9000 problems. You can also reproduce this by adding an extremely long string to the packet:

self._send_packet({'name': 'handshake', 'problems': problems, 'executors': runtimes, 'id': id, 'key': key, 'dummy': 'a' * 1000000000})

Btw, where do you get the info that "it's supposed to make sockets look like normal streams and handle incomplete transmission"? I can't seem to find it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because makefile is supposed to return a file object and file objects do not have this weird behaviour in which a write could not complete fully. In this case what I would actually recommend is just getting rid of the file objects and use socket.sendall.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@codecov-commenter
Copy link

codecov-commenter commented Jan 16, 2022

Codecov Report

Merging #984 (b9d64de) into master (258f8d9) will increase coverage by 2.60%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #984      +/-   ##
==========================================
+ Coverage   80.84%   83.44%   +2.60%     
==========================================
  Files         139      139              
  Lines        4714     4742      +28     
==========================================
+ Hits         3811     3957     +146     
+ Misses        903      785     -118     
Impacted Files Coverage Δ
dmoj/executors/base_executor.py 85.28% <0.00%> (-0.34%) ⬇️
dmoj/judge.py 52.83% <0.00%> (+1.25%) ⬆️
dmoj/executors/java_executor.py 85.35% <0.00%> (+2.02%) ⬆️
dmoj/executors/compiled_executor.py 77.71% <0.00%> (+4.00%) ⬆️
dmoj/cptbox/tracer.py 77.33% <0.00%> (+18.34%) ⬆️
dmoj/cptbox/handlers.py 100.00% <0.00%> (+25.00%) ⬆️
dmoj/cptbox/isolate.py 75.16% <0.00%> (+36.32%) ⬆️
dmoj/control.py 100.00% <0.00%> (+64.70%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 258f8d9...b9d64de. Read the comment docs.

If only some of the data was transmitted, writelines
does not attempt to send the remaining data, while
sendall continues to send data until either all data
has been sent or an error occurs.
@hieplpvip hieplpvip changed the title packet: repeat sending until all data has been delivered Use socket.sendall to send packets Jan 16, 2022
@leduythuccs
Copy link

Could DMOJ please merge this PR, I'm having the same issue :'(

@quantum5
Copy link
Member

ok to test

@quantum5 quantum5 merged commit f5cf158 into DMOJ:master Jan 22, 2022
@quantum5
Copy link
Member

Thanks for contributing to DMOJ!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants